﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Community.CsharpSqlite.SQLiteClient;
using MetaCollection.Framework.Model;
using System.IO;

namespace MetaCollection.SqliteRepository
{
	/// <summary>
	/// An implementation to store resources within a Sqlite database.
	/// </summary>
	/// <remarks>
	/// WARNING: Storing large BLOB data in a Sqlite database is not recommended.  Due to the implementation
	/// contraints involved when a blob is read the entire thing will be loaded into memory.  For large objects
	/// this will not be scalable.  There is no reason to use a Sqlite database for the binary resources.  Unlike
	/// the Sql Server ADONET version a Sqlite database can not reasonably handle the large files.
	/// 
	/// At any rate for the typical small fragment of HTML or for very low volume use the Sqlite database 
	/// resource provider will be fine.
	/// </remarks>
	public class ResourceRepository : MetaCollection.Framework.Repository.IResourceRepository
	{
		private string m_ConnectionString;
		private bool m_Initialized;

		/// <summary>
		/// Ensures the repository has been initialized.
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if <see cref="AdoNetItem.Initialize"/> has not been called.</exception>
		private void EnsureInitialized()
		{
			if (!m_Initialized)
				throw new InvalidOperationException("Resource Provider must be initialized prior to use.");
		}

		#region IRepository Members

		/// <summary>
		/// Initializes a new instance of the IItemRepository
		/// </summary>
		/// <param name="configData"></param>
		public void Initialize(string configData)
		{
			if (string.IsNullOrEmpty(configData))
				throw new ArgumentNullException("configData");
			m_ConnectionString = configData;
			m_Initialized = true;
		}

		#endregion

		#region IResourceRepository Members

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "This method returns the stream so it can not be disposed within this method"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Sql comes from a resource file and uses parameters")]
		public System.IO.Stream GetResourceStream(Framework.Model.IItemInstance item)
		{
			EnsureInitialized();

			if (item == null)
				throw new ArgumentNullException("item", "Item cannot be null.");

			Stream result;
			using (SqliteConnection sqlConn = new SqliteConnection(m_ConnectionString))
			{
				sqlConn.Open();
				using (var command = new SqliteCommand(Resources.ResourceSelectSql, sqlConn))
				{
					command.Parameters.Add("@id", item.Id.ToString().ToUpperInvariant());
					command.Parameters.Add("@version", item.Version);
					var reader = command.ExecuteReader();
					if (reader.Read())
					{
						if (!reader.IsDBNull(0))
						{
							var val = reader.GetValue(0);
							if (val is string) // because everything can be a string in Sqlite we check for that result.
								result = new MemoryStream(Encoding.UTF8.GetBytes((string)val));
							else
								result = new MemoryStream((byte[])val);
						}
						else
							result = System.IO.Stream.Null;
					}
					else
						return null;
				}
			}
			return result;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", Justification="Sql comes from a resource file and uses parameters")]
		public void SaveResourceStream(Framework.Model.IItemInstance item, System.IO.Stream data)
		{
			EnsureInitialized();

			if (item == null)
				throw new ArgumentNullException("item", "Item cannot be null.");
			if (data == null)
				throw new ArgumentNullException("data", "Data cannot be null.");

			using (SqliteConnection sqlConn = new SqliteConnection(m_ConnectionString))
			{
				sqlConn.Open();
				using (var command = new SqliteCommand(Resources.ResourceInsertSql, sqlConn))
				{
					command.Parameters.Add("@id", item.Id.ToString().ToUpperInvariant());
					command.Parameters.Add("@version", item.Version);
					command.Parameters.Add("@resource", data.GetBuffer());
					command.ExecuteNonQuery();
				}
			}

		}

		#endregion
	}

	public static class Extensions
	{
		private const int BUFFER_LENGTH = 8192;
		/// <summary>
		/// Reads a stream using a buffer and returns a byte[] for the stream data.
		/// </summary>
		/// <remarks>does not seek origin or close the stream</remarks>
		/// <param name="stream">stream to read</param>
		/// <returns>an array of bytes</returns>
		public static byte[] GetBuffer(this Stream stream)
		{
			if (stream == null || !stream.CanRead)
				return new byte[0];

			using (var ms = new MemoryStream())
			{
				var buffer = new byte[BUFFER_LENGTH];
				int readLength;
				while ((readLength = stream.Read(buffer, 0, buffer.Length)) > 0)
				{
					ms.Write(buffer, 0, readLength);
				}
				return ms.ToArray();
			}
		}
	}
}
